using System;

namespace MiniGert
{
  /// <summary>
  /// Vector defines a two dimensional Vector with all standard vector math routines.
  /// </summary>
  public class Vector
  {
    /// <summary>
    /// The internal field for the x component of the vector.
    /// </summary>
    protected float _x = 0.0f;
    /// <summary>
    /// The internal field for the y component of the vector.
    /// </summary>
    protected float _y = 0.0f;

    /// <summary>
    /// Creates a new Vector with X and Y set to 0.
    /// </summary>
    public Vector()
    {
    }

    /// <summary>
    /// Creates a new Vector and initializes X and Y with the given values.
    /// </summary>
    /// <param name="x">The x component.</param>
    /// <param name="y">The y component.</param>
    public Vector(float x, float y)
    {
      _x = x;
      _y = y;
    }

    /// <summary>
    /// Gets the X component of the vector.
    /// </summary>
    public float X
    {
      get { return _x; }
      set { _x = value; }
    }

    /// <summary>
    /// Gets the Y component of this vector.
    /// </summary>
    public float Y
    {
      get { return _y; }
      set { _y = value; }
    }

    /// <summary>
    /// Gets the magnitude (length) of the vector by using the pythogarean theorem.
    /// </summary>
    public float Magnitude
    {
      get { return (float)Math.Sqrt(_x * _x + _y * _y); }
    }

    /// <summary>
    /// Adds the values of the given vector to this vector.
    /// </summary>
    /// <param name="other">The vector to be added.</param>
    public void Add(Vector other)
    {
      _x += other._x;
      _y += other._y;
    }

    /// <summary>
    /// Subtracts the values of the given vector from this vector.
    /// </summary>
    /// <param name="other">The vector to be subtracted.</param>
    public void Subtract(Vector other)
    {
      _x -= other._x;
      _y -= other._y;
    }

    /// <summary>
    /// Normalizes this Vector. That is, X and Y are scaled so the magnitude of the vector is 1.0.
    /// </summary>
    public void Normalize()
    {
      float magnitude = Magnitude;
      _x /= magnitude;
      _y /= magnitude;
    }

    /// <summary>
    /// Scales the components of this vector by the given factor.
    /// </summary>
    /// <param name="factor">The scaling factor.</param>
    public void Scale(float factor)
    {
      _x *= factor;
      _y *= factor;
    }

    /// <summary>
    /// Calculates the dot/scalar product of this and the given vector.
    /// NOTE: The vectors have to normalized!
    /// </summary>
    /// <param name="other">The other vector.</param>
    /// <returns>The dot/scalar product of the two vectors.</returns>
    public float DotProduct(Vector other)
    {
      return _x * other._x + _y * other._y;
    }

    /// <summary>
    /// Inverts the direction of the vector.
    /// </summary>
    public void Invert()
    {
      _x *= -1f;
      _y *= -1f;
    }

    /// <summary>
    /// Reflects the vector by the given surface normal.
    /// </summary>
    /// <param name="normal">The normal to reflect the vector by. Has to be normalized.</param>
    public void Reflect(Vector normal)
    {
      float dot = this.DotProduct(normal);
      _x -= 2 * normal.X * dot;
      _y -= 2 * normal.Y * dot;
    }

    /// <summary>
    /// Adds the components of the two given vectors and returns the result.
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>A new vector with the result of the addition.</returns>
    public static Vector Add(Vector vec1, Vector vec2)
    {
      return new Vector(vec1._x + vec2._x, vec1._y + vec2._y);
    }

    /// <summary>
    /// Subtracts the components of the two given vectors and returns the result.
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>A new vector with the result of the subtraction.</returns>
    public static Vector Subtract(Vector vec1, Vector vec2)
    {
      return new Vector(vec1._x - vec2._x, vec1._y - vec2._y);
    }

    /// <summary>
    /// Normalizes the given vector and returns the result as a new vector.
    /// </summary>
    /// <param name="vec">The vector to be normalized.</param>
    /// <returns>The normalized vector.</returns>
    public static Vector Normalize(Vector vec)
    {
      float magnitude = vec.Magnitude;
      return new Vector(vec._x / magnitude, vec._y / magnitude);
    }

    /// <summary>
    /// Scales the components of given vector by the given factor and returns the result as a new vector.
    /// </summary>
    /// <param name="vec">The vector to be scaled.</param>
    /// <param name="factor">The scaling factor.</param>
    /// <returns>The scaled vector.</returns>
    public static Vector Scale(Vector vec, float factor)
    {
      return new Vector(vec._x * factor, vec._y * factor);
    }

    /// <summary>
    /// Calculates the dot/scalar product of the two given vectors.
    /// NOTE: The vectors have to be normalized!
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>The dot/scalar product of the two given vectors.</returns>
    public static float DotProduct(Vector vec1, Vector vec2)
    {
      return vec1._x * vec2._x + vec1._y * vec2._y;
    }

    /// <summary>
    /// Creates a new vector pointing from p1 to p2. The returned vector is not normalized.
    /// </summary>
    /// <param name="p1">The "start" point.</param>
    /// <param name="p2">The "direction" point.</param>
    /// <returns>A new vector pointing from p1 to p2.</returns>
    public static Vector FromPoints(Point p1, Point p2)
    {
      return new Vector(p2.X - p1.X, p2.Y - p1.Y);
    }

    /// <summary>
    /// Inverts the given vector and returns the result as a new vector.
    /// </summary>
    /// <param name="vec">The vector to be inverted.</param>
    /// <returns>The inverted vector.</returns>
    public static Vector Invert(Vector vec)
    {
      return new Vector(vec._x * -1f, vec._y * -1f);
    }

    /// <summary>
    /// Reflects the given vector by the given surface normal.
    /// </summary>
    /// <param name="vec">The vector to be reflected.</param>
    /// <param name="normal">The surface normal. Has to be noramlized.</param>
    /// <returns>A new vector that is the result of the reflection.</returns>
    public static Vector Reflect(Vector vec, Vector normal)
    {
      float dot = vec.DotProduct(normal) * 2;
      return new Vector(vec.X - normal.X * dot, vec.Y - normal.Y * dot);
    }

    /// <summary>
    /// Calculates the half vector, which is the vector at the half angle 
    /// between the two given vectors.
    /// </summary>
    /// <param name="vec1">The first vector.</param>
    /// <param name="vec2">The second vector.</param>
    /// <returns>The calculated half vector.</returns>
    public static Vector HalfVector(Vector vec1, Vector vec2)
    {
      return Vector.Normalize(Vector.Add(vec1, vec2));
    }

  }
}
